package com.eight.lotteryservice.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.eight.lotteryservice.config.MsgBean;
import com.eight.lotteryservice.config.RabbitKeys;
import com.eight.lotteryservice.config.RedisKeys;
import com.eight.lotteryservice.config.ResBean;
import com.eight.lotteryservice.entity.*;
import com.eight.lotteryservice.mapper.RedGameMapper;
import com.eight.lotteryservice.mapper.RedGameProductMapper;
import com.eight.lotteryservice.mapper.RedProductMapper;
import com.eight.lotteryservice.mapper.RedUserMapper;
import com.eight.lotteryservice.utils.RedisUtil;
import feign.QueryMap;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author eight
 * @since 2022-08-20
 */
@RestController
@RequestMapping("/")
@CrossOrigin
public class LotteryController {

    @Resource
    RedGameMapper redGameMapper;
    @Resource
    RedProductMapper redProductMapper;
    @Resource
    RedGameProductMapper redGameProductMapper;
    @Resource
    RabbitTemplate rabbitTemplate;
    @Resource
    private RedisUtil redisUtil;

    private final static Logger log = LoggerFactory.getLogger(LotteryController.class);

    @GetMapping("hot")
    public void execute() {
        //当前时间
        Date now = new Date();
//       1。 开始时间大于当前时间
//       2. 小雨等于 当前时间+1min
        //查询将来1分钟内要开始的活动
        QueryWrapper<RedGame> queryWrapper = new QueryWrapper<>();
//        queryWrapper.ge("starttime", now);
//        queryWrapper.le("starttime", DateUtils.addMinutes(now, 1));
//        List<RedGame> list = redGameMapper.selectList(queryWrapper);
        List<RedGame> list = redGameMapper.selectList(null);
        log.info("game list :"+list);
        if (list.size() == 0) {
            //没有查到要开始的活动
            log.info("game list scan : size = 0");
            return;
        }
        log.info("game list scan : size = {}", list.size());
        //有相关活动数据，则将活动数据预热，进redis
        // 只有一个场次，不可能同一时间有多个场次
        RedGame game = list.get(0);

        //活动开始时间
        long start = game.getStarttime().getTime();
        //活动结束时间
        long end = game.getEndtime().getTime();
        //计算活动结束时间到现在还有多少秒，作为redis key过期时间
        long expire = (end - now.getTime()) / 1000;

        redisUtil.set("_expire",expire,expire);
//            long expire = -1; //永不过期
        //活动持续时间（ms）
        long duration = end - start;

        int now_rounds = game.getRound();
//      Redis 设置 k:userid_rounds v:now_rounds
        //活动基本信息
        game.setStatus(true);
//      redis  设置get
        redisUtil.set("_game",game);
//      redisUtil.set(RedisKeys.INFO+game.getId(),game,-1);
        log.info("load game info:{},{},{},{}", game.getId(), game.getTitle(), game.getStarttime(), game.getEndtime());
        //活动奖品信息
        /**
         * 这里可以从只需要三个奖品
         */
        //  1. 查询一二三等奖的份数
        //  num_1,num_2,num_3
//        QueryWrapper<RedProduct> productQueryWrapper = new QueryWrapper<>();
//        /**
//         * 这里要后续和game关联
//         */
//        List<RedProduct> products = redProductMapper.selectList(productQueryWrapper);
        QueryWrapper<RedGameProduct> gameProductQueryWrapper = new QueryWrapper<>();
        int gameid = game.getId();
        gameProductQueryWrapper.eq("gameid",gameid);
        List<RedGameProduct> redGameProducts = redGameProductMapper.selectList(gameProductQueryWrapper);

        List<Long> tokenList = new ArrayList();
        redGameProducts.forEach(redGameProduct -> {
//            Integer gameid = redGameProduct.getGameid();
            Integer product_id = redGameProduct.getProductid();
            QueryWrapper<RedProduct> productQueryWrapper = new QueryWrapper<>();
            productQueryWrapper.eq("id", product_id);
            List<RedProduct> products = redProductMapper.selectList(productQueryWrapper);
            products.forEach(product ->{
                log.info("奖品:"+product.getName());
                log.info("个数:"+redGameProduct.getAmount());
                for( int i =0 ; i < redGameProduct.getAmount() ;i++){
                    long rnd = start + new Random().nextInt((int) duration);
                    //为什么乘1000，再额外加一个随机数呢？ - 防止时间段奖品多时重复
                    //记得取令牌判断时间时，除以1000，还原真正的时间戳
                    // token是时间错
                    long token = rnd * 1000 + new Random().nextInt(999);
                    //将令牌放入令牌桶
                    tokenList.add(token);
                    //以令牌做key，对应的商品为value，创建redis缓存
//                   令牌key：商品value
//                    log.info("token -> game : {} -> {}", token / 1000, redProductMapper.selectById(product.getId()).getName());
                    //token到实际奖品之间建立映射关系
                    /**
                     * redis token和奖品的关系
                     * key:
                     * value: reProduct
                     */
                    redisUtil.set(game.getId() +"_"+token,product,expire);

                }

            });
        });
        //  2. products.push(Products(1)*num1)
        // products.push(Products(2)*num2)
        // products.push(Products(3)*num3)
        // List<CardProductDto> products = gameLoadMapper.getByGameId(game.getId());

//        Map<Integer,CardProduct> productMap = new HashMap<>(products.size());
//        products.forEach(p -> productMap.put(p.getId(),p));
//        log.info("load product type:{}",productMap.size());
//
//        //奖品数量等配置信息
//        CardGameProductExample productExample = new CardGameProductExample();
//        productExample.createCriteria().andGameidEqualTo(game.getId());
//        List<CardGameProduct> gameProducts = gameProductMapper.selectByExample(productExample);
//        log.info("load bind product:{}",gameProducts.size());

        //令牌桶


        //排序后放入redis队列
        Collections.sort(tokenList);
        log.info("load tokens:{}", tokenList);

        // 从右侧压入队列，从左到右，时间戳逐个增大
        redisUtil.rightPushAll(RedisKeys.BUCKET + game.getId(),tokenList);
        redisUtil.expire(RedisKeys.BUCKET + game.getId(),expire);

//        List<>redisUtil.get(RedisKeys.TOKENS + game.getId());

        /**
         * 不需要策略？
         */
        //奖品策略配置信息
//        CardGameRulesExample rulesExample = new CardGameRulesExample();
//        rulesExample.createCriteria().andGameidEqualTo(game.getId());
//        List<CardGameRules> rules = gameRulesMapper.selectByExample(rulesExample);
//        //遍历策略，存入redis hset
//        rules.forEach(r -> {
//            redisUtil.hset(RedisKeys.MAXGOAL +game.getId(),r.getUserlevel()+"",r.getGoalTimes());
//            redisUtil.hset(RedisKeys.MAXENTER +game.getId(),r.getUserlevel()+"",r.getEnterTimes());
//            log.info("load rules:level={},enter={},goal={}",r.getUserlevel(),r.getEnterTimes(),r.getGoalTimes());
//        });
//        redisUtil.expire(RedisKeys.MAXGOAL +game.getId(),expire);
//        redisUtil.expire(RedisKeys.MAXENTER +game.getId(),expire);


        //活动状态变更为已预热，禁止管理后台再随便变动
        game.setStatus(true);
        redGameMapper.updateById(game);
//}

    }

    @Resource
    RedUserMapper redUserMapper;

    @GetMapping("test")
    public MsgBean test(HttpServletResponse response){
        log.debug("log:中奖!");
        response.addHeader("Access-Control-Allow-Origin","*");
        return new MsgBean(1,"一等奖");
    }

    @PostMapping("turns")
    public Integer tuns(HttpServletResponse response){
        RedGame redGame = (RedGame) redisUtil.get("_game");
        return redGame.getRound();
    }

    @PostMapping("start")
    public ResBean start(Integer userid ){
        RedGame redGame = (RedGame) redisUtil.get("_game");
        Integer user_round = (Integer) redisUtil.get(userid+"_"+redGame.getId()  + "_round" );
        Integer game_round = redGame.getRound();
        ResBean resBean = new ResBean();
        resBean.setCode(0);
        if(user_round == game_round) {
            redisUtil.set(userid+"_"+redGame.getId()  + "_round",user_round+1);
            resBean.setCode(1);
        }
        return resBean;
    }

    @CrossOrigin
    @PostMapping("/interface")
    public MsgBean act(Integer uid) {
//       测试时要写死
        int userid = uid;
        log.info("用户"+userid+"准备抽奖---->");
        Date now = new Date();
        //获取活动基本信息
        RedGame game = (RedGame) redisUtil.get("_game");
        log.info("当前Game："+game);
        //判断活动是否开始
        //如果活动信息还没加载进redis，无效
        //如果活动已经加载，预热完成，但是开始时间 > 当前时间，也无效
//        todo 测试不判断获得是否结束
//        if (game == null || game.getStarttime().after(now)) {
//            return new MsgBean(-1, "活动未开始");
//        }
//        //判断活动是否已结束
//        if (now.after(game.getEndtime())) {
//            return new MsgBean(-1, "活动已结束");
//        }
        //获取当前用户
        /**
         * 怎么从session中获得用户id
         */
//        HttpSession session = request.getSession();
//      获得前端 userid.





        // redis获得用户已中奖次数count 来判断保底 key:userid_xx,value:count
//         redis 判断是否获得过一等奖和当前id的轮次，是否中过奖key:userid_xx value:hasOne
//         redis 判断轮次              key:userid_xx value:tunrs
        Integer gameid = game.getId();
//        记录中奖信息
        Map map = new HashMap(2);
        map.put("gameid",gameid);
        map.put("userid",userid);
        map.put("hittime",now);
        /**
         * 获得用户是否获得过一等奖励的数据，如果获得过一等奖hasOne，后续不能再获得一等奖
         */
//        todo redis 判断是否获得过一等奖和当前id的轮次，是否中过奖
        Integer count = (Integer) redisUtil.get(userid+"_"+gameid  + "_count" );
        Integer hasOne = (Integer) redisUtil.get(userid+"_"+gameid  + "_hasOne" );
        /**
         * user_round是用户参加过几轮
         * game_turns 是当前游戏再第几轮
         */
        Integer user_round = (Integer) redisUtil.get(userid+"_"+gameid  + "_round" );
        Integer game_turns = (Integer) game.getRound();
//        redisUtil.set("_expire",expire,expire);
        Long expire = (Long)redisUtil.get("_expire");
        if(count == null){
            count = 0;
        }
        if(hasOne == null){
            hasOne =0;
        }
        if(user_round == null){
            user_round = 1;
        }
        double randomDouble = Math.random();
        log.info("randomDouble："+randomDouble);
        if (randomDouble > 0.3) {
            log.info("没进奖池");
//            40%概率纪念奖或没中过奖都可以获得纪念奖
            if (randomDouble > 0.6 || (count == 0 && game_turns == 3 && user_round == game_turns)) {
                /**
                 * 这里我们需要通过redis获得用户的中奖次数count，以及是否参加了两个场次。
                 * count =0 && 参加了两个场次：必中
                 */
                //返回给前台中奖信息
                RedProduct redProduct = new RedProduct();
                redProduct.setLevel(4);
                redisUtil.set(userid+"_"+gameid + "_count_" , count + 1,expire);
                log.info("用户"+userid+"得到纪念奖");
                map.put("productid",4);
                msgHandler(map);
                return new MsgBean(4,"纪念奖");
//                return new ApiResult(1, "纪念奖", redProduct);
            } else {
//                没中奖
                log.info("用户"+userid+"感谢参与");
                map.put("productid",5);
                msgHandler(map);
                return new MsgBean(5,"感谢参与");
//                return new ApiResult(-1, "感谢参与", null);
            }
        } else {
            //以上校验全部通过，准许进入抽奖逻辑
            log.info("进奖池了");
//          redis  令牌桶抽奖
            Long token = (Long) redisUtil.leftPop(RedisKeys.BUCKET + gameid);
            if (token == null) {
                map.put("productid",4);
                msgHandler(map);
                return new MsgBean(4,"纪念奖");
//                return new ApiResult(1, "纪念奖", null);
            }
            //判断令牌时间戳大小，即是否中奖
            //这里记住，取出的令牌要除以1000，参考job项目，令牌生成部分
//        if (now.getTime() < token/1000){
//            //当前时间小于令牌时间戳，说明奖品未到发放时间点，放回令牌，返回未中奖
//            redisUtil.leftPush(RedisKeys.TOKENS+gameid,token);
//            return new ApiResult(0," 感谢参与",null);
//        }

            //以上逻辑走完，说明很幸运，中奖了！
            //抽中的奖品：
//            RedProduct product = (RedProduct) redisUtil.get(RedisKeys.TOKEN + gameid +"_"+token);

            RedProduct product = (RedProduct) redisUtil.get(gameid+"_"+token);
            //中奖次数加1
            /**
             * 在这个位置可以添加一等奖判断，如果product == 一等奖，且hasOne == True，则放回奖品，再抽一次，以此循环直到抽到的不是一等奖。
             * 放回代码：redisUtil.leftPush(RedisKeys.TOKENS+gameid,token);
             */
            log.info("token:"+token);
            log.info(product.getName());
            log.info("一等奖判断验证：hasOne:"+hasOne+",product.getLevel():"+product.getLevel());
            if (hasOne > 1 && product.getLevel().equals(1)) {
//          放回代码：redisUtil.leftPush(RedisKeys.TOKENS+gameid,token);
                redisUtil.rightPush(RedisKeys.BUCKET + gameid, token);
//            重新走抽奖逻辑
                return act(uid);
            }
            if (product.getLevel().equals(1)) {
                //           redis 中一等奖次数 hasOne ++
                redisUtil.set(userid+"_"+gameid +"_hasOne",hasOne+1,expire);
            }
//            redisUtil.incr(RedisKeys.USERHIT+gameid+"_"+userid,1);

            count++;
            redisUtil.set(userid+"_"+gameid +"_count", count,expire);

            /**
             * 头发消息队列，消息包括场次的id
             * 中奖的时间
             * 产品的id
             * 用户的id
             */
            //投放消息给队列，中奖后的耗时业务，交给消息模块处理
//            RedUserHit hit = new RedUserHit();
//            hit.setGameid(gameid);
//            hit.setHittime(now);
//            hit.setProductid(product.getId());
//            hit.setUserid(userid);



            map.put("productid",product.getId());
            msgHandler(map);

            /**
             * 返回信息
             */
//            RedProduct product = null;
            //返回给前台中奖信息
            log.info("用户"+userid+"中奖："+product);
            return new MsgBean(product.getId(),product.getName());
//            return new ApiResult(1, "恭喜中奖", product);
        }


    }

    void msgHandler(Map map){

        //           消息队列处理
        rabbitTemplate.convertAndSend(RabbitKeys.EXCHANGE_DIRECT,RabbitKeys.QUEUE_HIT, map);

    }
}

